package org.fujay.langchain4jserver.application.service.impl;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.fujay.commons.core.util.JsonUtils;
import org.fujay.commons.langchain4j.param.Message;
import org.fujay.langchain4jserver.application.assembler.ConversationAssembler;
import org.fujay.langchain4jserver.application.command.StartConversationCommand;
import org.fujay.langchain4jserver.application.dto.ConversationDetailDTO;
import org.fujay.langchain4jserver.application.dto.ConversationSummaryDTO;
import org.fujay.langchain4jserver.application.dto.MessageDTO;
import org.fujay.langchain4jserver.application.dto.SendMessageRequest;
import org.fujay.langchain4jserver.application.service.ConversationService;
import org.fujay.langchain4jserver.domain.application.aggregate.AppAggregate;
import org.fujay.langchain4jserver.domain.application.exception.ApplicationNotFoundException;
import org.fujay.langchain4jserver.domain.application.repository.AppRepository;
import org.fujay.langchain4jserver.domain.application.vo.AppId;
import org.fujay.langchain4jserver.domain.conversation.aggregate.ConversationAggregate;
import org.fujay.langchain4jserver.domain.conversation.entity.MessageEntity;
import org.fujay.langchain4jserver.domain.conversation.exception.ConversationNotFoundException;
import org.fujay.langchain4jserver.domain.conversation.repository.ConversationRepository;
import org.fujay.langchain4jserver.domain.conversation.service.ConversationProcessor;
import org.fujay.langchain4jserver.domain.conversation.vo.ConversationId;
import org.fujay.langchain4jserver.domain.conversation.vo.TokenUsage;
import org.fujay.langchain4jserver.domain.conversation.vo.UserId;
import org.fujay.langchain4jserver.domain.model.aggregate.Model;
import org.fujay.langchain4jserver.domain.model.exception.ModelNotFoundException;
import org.fujay.langchain4jserver.domain.model.repository.ModelRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import reactor.core.publisher.Flux;

import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;

@Slf4j
@Service
@RequiredArgsConstructor
public class ConversationServiceImpl implements ConversationService {

    private final ConversationRepository conversationRepository;
    private final AppRepository appRepository;
    private final ModelRepository modelRepository;
    private final ConversationProcessor conversationProcessor;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ConversationSummaryDTO startConversation(StartConversationCommand command) {
        log.info("开始新对话，用户ID: {}, 应用ID: {}", "用户Id", command.getAppId());
        ConversationId conversationId = ConversationId.create();
        ConversationAggregate conversation =
                ConversationAggregate.create(
                        conversationId, UserId.create(), command.getAppId());

        MessageDTO initialMessageDto = command.getInitialMessage();
        if (initialMessageDto != null && initialMessageDto.getContent() != null) {
            conversation.addUserMessage(initialMessageDto);
        }
        conversationRepository.save(conversation);
        log.info("对话已开始，ID: {}", conversationId.value());
        return ConversationAssembler.toSummaryDTO(conversation);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Flux<Message> sendMessage(
            ConversationId conversationId, SendMessageRequest messageRequest) {
        log.info("向对话 {} 发送消息", conversationId.value());
        // 对话聚合根
        ConversationAggregate conversation =
                conversationRepository
                        .findById(conversationId)
                        .orElseThrow(() -> new ConversationNotFoundException(conversationId));
        // 应用聚合根
        AppAggregate aggregate =
                appRepository.findAppShallow(conversation.getAppId())
                        .orElseThrow(() -> new ApplicationNotFoundException(conversation.getAppId()));
        // 模型聚合根
        Model model =
                modelRepository
                        .findById(aggregate.getApp().getModelId())
                        .orElseThrow(() -> new ModelNotFoundException(aggregate.getApp().getModelId()));

        MessageDTO messageDTO = new MessageDTO();
        messageDTO.setContent(messageRequest.getContent());
        messageDTO.setImageUrls(messageRequest.getImageUrls());

        MessageEntity userMessage = conversation.addUserMessage(messageDTO);
        // userId 从登陆上下文中获取
        return conversationProcessor.generateResponse(
                userMessage,
                conversation,
                aggregate.getApp(),
                model,
                doOnCompleteConsumer(null, conversationId.value()));
    }

    @Override
    public Optional<ConversationDetailDTO> getConversation(ConversationId conversationId) {
        log.debug("获取对话详情，ID: {}", conversationId.value());
        Optional<ConversationAggregate> aggregateOpt = conversationRepository.findById(conversationId);
        return aggregateOpt.map(ConversationAssembler::toDetailDTO);
    }

    @Override
    public List<ConversationSummaryDTO> listConversations(UserId userId, AppId appId) {
        log.debug("列出对话，用户ID: {}, 应用ID: {}", userId, appId);
        List<ConversationAggregate> aggregates = conversationRepository.findByUserAndApp(userId, appId);
        return ConversationAssembler.toSummaryDTOList(aggregates);
    }

    @Override
    public void deleteConversation(ConversationId conversationId) {
        log.info("删除对话，ID: {}", conversationId.value());
        conversationRepository.deleteById(conversationId);
        log.info("对话 {} 已删除", conversationId.value());
    }

    /**
     * 回话回调，处理消息
     */
    public Consumer<Message> doOnCompleteConsumer(Long userId, Long conversationId) {
        return message -> {
            // FIXME tokenUsage 逻辑不完善
            ConversationAggregate conversation =
                    conversationRepository
                            .findById(new ConversationId(conversationId))
                            .orElseThrow(
                                    () -> new ConversationNotFoundException(new ConversationId(conversationId)));
            conversation.addAiMessage(message.getContent(), TokenUsage.zero(), null);
            conversationRepository.save(conversation);
            log.error("对话进入回调,用户Id:{},会话Id:{},消息:{}", userId, conversationId, JsonUtils.toJsonString(message));
        };
    }
}
